核心概念
# 核心概念
[TOC]
# 一、异步I/O
Node将异步作为主要编程和设计理念,可以更好的并发。
- 利用单线程,远离多线程死锁、状态同步等问题。
- 利用异步I/O,让单线程远离阻塞,以更好地使用CPU。
# 1.1 不同的I/O类型对应的开销
I/O类型 | 花费的CPU时钟周期 |
---|---|
CPU一级缓存 | 3 |
CPU二级缓存 | 14 |
内存 | 250 |
硬盘 | 41000000 |
网络 | 240000000 |
- I/O是昂贵的,分布式I/O是更昂贵的。
- 只有后端能够快速响应资源,才能让前端的体验好。
# 1.2 异步I/O的调用
- I/O的调用不再阻塞后续运算,将原有等待I/O完成的这段时间分配给其余需要的业务去执行。
# 1.3 阻塞与非阻塞
操作系统内核对于I/O只有两种方式:阻塞与非阻塞。
# 1.3.1 阻塞I/O
- 在调用阻塞I/O时,应用程序需要等待I/O完成才返回结果。
- 特点:调用之后一定要等到系统内核层面完成所有操作后,调用才结束。
- 缺点:造成CPU等待I/O,浪费等待时间,CPU处理能力不能得到充分利用。
# 1.3.2 非阻塞I/O
- 非阻塞I/O不带数据直接返回,要获取数据,还需要通过文件描述符再次读取。
- 非阻塞I/O返回之后,CPU的时间片可以用来处理其他事务,性能得到提升。
- 缺点:为了获取完整数据,应用程序需要通过轮询来确认是否完成。
# 1.3.3 Node的阻塞与非阻塞
- 阻塞方法同步执行,非阻塞方法异步执行。
// 读取的同步文件
const fs = require('fs');
const data = fs.readFileSync('/file.md'); // blocks here until file is read
1
2
3
2
3
// 等效的异步读取
// fs.readFile()是非阻塞的
const fs = require('fs');
fs.readFile('/file.md', (err, data) => {
if (err) throw err;
});
1
2
3
4
5
6
2
3
4
5
6
- 避免混合阻塞和非阻塞代码的危险
// fs.unlinkSync()可能在fs.readFile()之前执行
// 即file.md在实际读取之前删除
const fs = require('fs');
fs.readFile('/file.md', (err, data) => {
if (err) throw err;
console.log(data);
});
fs.unlinkSync('/file.md');
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
// 在回调中放置非阻塞调用,保证正确的操作顺序
const fs = require('fs');
fs.readFile('/file.md', (readFileErr, data) => {
if (readFileErr) throw readFileErr;
console.log(data);
fs.unlink('/file.md', (unlinkErr) => {
if (unlinkErr) throw unlinkErr;
});
});
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
# 二、事件循环
事件循环允许Node.js执行非阻塞I / O操作 。
- 在node中,除了JavaScript是单线程外,Node自身其实是多线程的,只是I/O线程使用的CPU较少。
- 除了用户代码无法并行执行外,所有的I/O(磁盘I/O和网络I/O等)则是可以并行起来的。
- 整个异步I/O的流程如下:
# 三、非I/O的异步API
# 3.1 定时器
# 3.2 process.nextTick()
- 可以实现立即异步执行一个任务。
process.nextTick(fn)
// 类似于
setTimeout(function () {
// TODO
}, 0)
1
2
3
4
5
6
2
3
4
5
6
setTimeout(fn,0)
较为浪费性能,需要动用红黑树、创建定时器对象和迭代等操作;并且由于事件循环自身的特点,定时器的精确度不够。相比之下,process.nextTick()
操作较为轻量。
# 3.3 setImmediate()
类似于process.nextTick()
,都是将回调函数延迟执行。
setImmediate(fn)
1
# 3.3.1 与process.nextTick()的异同
- 执行顺序:
process.nextTick()
优于setImmdiate()
。
setImmediate(() => {
console.log(`setImmediate1`)
})
process.nextTick(() => {
console.log(`nextTick1`)
})
console.log('console')
// console
// nextTick1
// setImmediate1
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
process.nextTick()
属于idle观察者,setImmediate()
属于check观察者。在每一轮轮询检查中,idle观察者先于I/O观察者,I/O观察者先于check观察者。
- 具体实现:
process.nextTick()
的回调函数保存在一个数组中,setImmdiate()
的结果则保存在链表中。 - 行为:
process.nextTick()
再每轮循环中会将数组中的回调函数全部执行完;而setImmdiate()
在每轮循环中执行链表中的一个回调函数,可保证每轮循环能够较快执行结束,防止CPU占用过多而阻塞后续I/O调用的情况。